#include <stdio.h>
#include <SDL.h>
#include "fake437.h"

#define BIGCHAR_LEFT 16
#define BIGCHAR_TOP 3
#define CHARS_LEFT 48
#define CHARS_TOP 4
#define CHARDEF_LEFT 10
#define CHARDEF_TOP 20

#define ACTIVE_FG 12
#define ACTIVE_BG 2
#define INACTIVE_FG 15
#define INACTIVE_BG 0

#define BACK_CH 177
#define BACK_FG 8
#define BACK_BG 3
static void background(FAKE437 *f437){
  int i;
  int j;
  for(i = 0 ; i < f437->width ; i++)
    for(j = 0 ; j < f437->height ; j++)
      f437_SetCharColor(f437, i, j, BACK_CH, BACK_FG, BACK_BG);
}

#define BORDER_FG 14
#define BORDER_BG 1
static void border(FAKE437 *f437, int x1, int y1, int x2, int y2){
  int i;
  f437_SetCharColor(f437, x1, y1, 201, BORDER_FG, BORDER_BG);
  f437_SetCharColor(f437, x1, y2, 200, BORDER_FG, BORDER_BG);
  f437_SetCharColor(f437, x2, y1, 187, BORDER_FG, BORDER_BG);
  f437_SetCharColor(f437, x2, y2, 188, BORDER_FG, BORDER_BG);
  for(i = 1 ; i < x2 - x1 ; i++){
    f437_SetCharColor(f437, x1 + i, y1, 205, BORDER_FG, BORDER_BG);
    f437_SetCharColor(f437, x1 + i, y2, 205, BORDER_FG, BORDER_BG);
  }
  for(i = 1 ; i < y2 - y1 ; i++){
    f437_SetCharColor(f437, x1, y1 + i, 186, BORDER_FG, BORDER_BG);
    f437_SetCharColor(f437, x2, y1 + i, 186, BORDER_FG, BORDER_BG);
  }
}

static void asciitable(FAKE437 *f437, int activech){
  int i;
  for(i = 0 ; i < F437_NUM_CHARS ; i++){
    if(i == activech)
      f437_SetCharColor(f437, i % 16, i / 16, i, ACTIVE_FG, ACTIVE_BG);
    else
      f437_SetCharColor(f437, i % 16, i / 16, i, INACTIVE_FG, INACTIVE_BG);
  }
}

#define BIGPIXEL_WIDTH 2
static void bigchar(FAKE437 *screen, FAKE437 *chars,
		    int activech, int activex, int activey){
  int chunk;
  int row;
  int pixel;
  unsigned int hex;
  int x;
  int i;
  char fragment[2] = "\0\0";
  char *chardef = f437_GetCharDef(chars, activech);
  if(chardef == NULL) return;
  for(row = 0 ; row < F437_CHAR_HEIGHT ; row++)
    for(chunk = 0 ; chunk < F437_CHAR_WIDTH / 4 ; chunk++){
      fragment[0] = chardef[row*2 + chunk];
      sscanf(fragment, "%X", &hex);
      for(pixel = 0 ; pixel < 4 ; pixel++){
	x = chunk * 4 + pixel;
	for(i = 0 ; i < BIGPIXEL_WIDTH ; i++){
	  f437_SetCharColor(screen,
			    BIGCHAR_LEFT + x * BIGPIXEL_WIDTH + i,
			    BIGCHAR_TOP + row,
			    ((1 << (3 - pixel)) & hex) ? 219 : 32,
			    ((x == activex) && (row == activey))
			    ? ACTIVE_FG
			    : INACTIVE_FG,
			    ((x == activex) && (row == activey))
			    ? ACTIVE_BG
			    : INACTIVE_BG);
	}
      }
			  
    }
  free(chardef);
}

static void drawstring(FAKE437 *f437, char *str, int x, int y,
		       Uint8 fg, Uint8 bg){
  int i;
  int len = strlen(str);
  for(i = 0 ; i < len ; i++)
    f437_SetCharColor(f437, x + i, y, str[i], fg, bg);
}

#define CHARDEF_FG 15
#define CHARDEF_BG 0
static void chardef(FAKE437 *screen, FAKE437 *chars, int activech){
  char *chardef = f437_GetCharDef(chars, activech);
  if(chardef == NULL) return;
  drawstring(screen, chardef, CHARDEF_LEFT, CHARDEF_TOP,
	     CHARDEF_FG, CHARDEF_BG);
  free(chardef);
}

static void togglepixel(FAKE437 *chars, int activech, int activex, int activey){
  int hex;
  char *chardef = f437_GetCharDef(chars, activech);
  int index = activey * (F437_CHAR_WIDTH / 4) + activex / 4;
  char fragment[2] = "\0\0";
  if(chardef == NULL) return;
  fragment[0] = chardef[index];
  sscanf(fragment, "%X", &hex);
  hex ^= 1 << (3 - (activex % 4));
  sprintf(fragment, "%X", hex);
  chardef[index] = fragment[0];
  f437_SetCharDef(chars, activech, chardef);
  free(chardef);
}

#define STATUS_FG 15
#define STATUS_A_BG 3
#define STATUS_B_BG 1
static void statusbar(FAKE437 *screen){
  int y = screen->height - 1;
  drawstring(screen, " \x18\x19\x1A\x1B: Cursor ",
	     0, y, STATUS_FG, STATUS_A_BG);
  drawstring(screen, " SPACE: Flip pixel ",
	     14, y, STATUS_FG, STATUS_B_BG);
  drawstring(screen, " SHIFT+\x18\x19\x1A\x1B: Char ",
	     33, y, STATUS_FG, STATUS_A_BG);
  drawstring(screen, " CTRL+X: SaveQuit ",
	     51, y, STATUS_FG, STATUS_B_BG);
  drawstring(screen, " ESC: Quit ",
	     69, y, STATUS_FG, STATUS_A_BG);
}

 void load(FAKE437 *chars, char *file){
  int i;
  FILE *f;
  f437_charDefs *chardef;
  if((f = fopen(file, "r")) == NULL) return;
  if((chardef = f437_GetCharDefs(chars)) == NULL){
    fclose(f);
    return;
  }
  for(i = 0 ; i < F437_NUM_CHARS ; i++){
    if(feof(f)) break;
    fscanf(f, "%28s", (*chardef)[i]);
  }
  f437_SetCharDefs(chars, (const f437_charDefs*)chardef);
  free(chardef);
  fclose(f);
}

static void save(FAKE437 *chars, char *file){
  int i;
  FILE *f;
  f437_charDefs *chardef;
  if((f = fopen(file, "w")) == NULL) return;
  if((chardef = f437_GetCharDefs(chars)) == NULL){
    fclose(f);
    return;
  }
  for(i = 0 ; i < F437_NUM_CHARS ; i++)
    fprintf(f, "%s\n", (*chardef)[i]);
  fclose(f);
}

static void sdlerror(){
  fprintf(stderr, "SDL Error: %s\n", SDL_GetError());
}

int main(int argc, char *argv[]){
  SDL_Surface *screen;
  SDL_Surface *chars;
  FAKE437 *fscreen;
  FAKE437 *fchars;
  SDL_Event ev;
  int done = 0;
  int activech = 0;
  int activex = 0;
  int activey = 0;
  SDL_Rect r;
  if(argc != 2){
    printf("Usage: %s filename", argv[0]);
    return 1;
  }

  r.x = CHARS_LEFT * F437_CHAR_WIDTH;
  r.y = CHARS_TOP * F437_CHAR_HEIGHT;

  if(SDL_Init(SDL_INIT_VIDEO) == -1) goto err0;
  SDL_WM_SetCaption("Charset Editor", "Charset Editor");
  if(((screen = SDL_SetVideoMode(F437_DEFAULT_WIDTH_PX, F437_DEFAULT_HEIGHT_PX,
				 32, 0)) == NULL) ||
     ((fscreen = f437_Create(screen, F437_DEFAULT_WIDTH,
			     F437_DEFAULT_HEIGHT)) == NULL)) goto err1;
  if((chars = SDL_CreateRGBSurface(SDL_SWSURFACE,
				   16 * F437_CHAR_WIDTH,
				   16 * F437_CHAR_HEIGHT,
				   32,
				   screen->format->Rmask,
				   screen->format->Gmask,
				   screen->format->Bmask,
				   screen->format->Amask)) == NULL) goto err2;
  if((fchars = f437_Create(chars, 16, 16)) == NULL) goto err3;
  load(fchars, argv[1]);

  background(fscreen);
  border(fscreen, BIGCHAR_LEFT - 1, BIGCHAR_TOP - 1,
	 BIGCHAR_LEFT + F437_CHAR_WIDTH * 2, BIGCHAR_TOP + F437_CHAR_HEIGHT);
  border(fscreen, CHARS_LEFT - 1, CHARS_TOP - 1,
	 CHARS_LEFT + 16, CHARS_TOP + 16);
  border(fscreen, CHARDEF_LEFT - 1, CHARDEF_TOP - 1,
	 CHARDEF_LEFT + F437_CHAR_HEIGHT * 2, CHARDEF_TOP + 1);
  statusbar(fscreen);

  while(!done){
    while(SDL_PollEvent(&ev))
      if(ev.type == SDL_KEYDOWN){
	if(ev.key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT))
	  switch(ev.key.keysym.sym){
	  case SDLK_UP:
	    if(activech > 15) activech -= 16;
	    break;
	  case SDLK_DOWN:
	    if(activech < 240) activech += 16;
	    break;
	  case SDLK_LEFT:
	    if(activech % 16 > 0) activech--;
	    break;
	  case SDLK_RIGHT:
	    if(activech % 16 < 15) activech++;
	    break;
	  default:
	    break;
	  }
	else if(ev.key.keysym.mod & (KMOD_LCTRL | KMOD_RCTRL))
	  switch(ev.key.keysym.sym){
	  case SDLK_x:
	    save(fchars, argv[1]);
	    done = 1;
	    break;
	  default:
	    break;
	  }
	else
	  switch(ev.key.keysym.sym){
	  case SDLK_UP:
	    if(activey > 0) activey--;
	    break;
	  case SDLK_DOWN:
	    if(activey < F437_CHAR_HEIGHT - 1) activey++;
	    break;
	  case SDLK_LEFT:
	    if(activex > 0) activex--;
	    break;
	  case SDLK_RIGHT:
	    if(activex < F437_CHAR_WIDTH - 1) activex++;
	    break;
	  case SDLK_SPACE:
	    togglepixel(fchars, activech, activex, activey);
	    break;
	  case SDLK_ESCAPE:
	    done = 1;
	    break;
	  default:
	    break;
	  }
      }
    bigchar(fscreen, fchars, activech, activex, activey);
    chardef(fscreen, fchars, activech);
    f437_Update(fscreen);
    asciitable(fchars, activech);
    f437_Update(fchars);
    SDL_BlitSurface(chars, NULL, screen, &r);
    SDL_Flip(screen);
  }

  f437_Destroy(fscreen);
  SDL_Quit();
  return 0;
 err3:
  SDL_FreeSurface(chars);
 err2:
  f437_Destroy(fscreen);
 err1:
  SDL_Quit();
 err0:
  sdlerror();
  return 1;
}
